/*
 * #%L
 * Server
 * %%
 * Copyright (C) 2011 Ractoc.com
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package com.ractoc.opengamefinder.server.plugins;

import com.ractoc.opengamefinder.api.messages.ChatCreateRoomMessage;
import java.util.Collections;
import java.util.List;

import org.apache.log4j.Logger;

import com.ractoc.opengamefinder.api.messages.ChatEnterRoomMessage;
import com.ractoc.opengamefinder.api.messages.ChatLeaveRoomMessage;
import com.ractoc.opengamefinder.api.messages.ChatLineMessage;
import com.ractoc.opengamefinder.api.results.ChatCreateRoomMessageResult;
import com.ractoc.opengamefinder.api.results.MessageResultsErrorCodes;
import com.ractoc.opengamefinder.api.results.ChatEnterRoomMessageResult;
import com.ractoc.opengamefinder.api.results.ChatLeaveRoomMessageResult;
import com.ractoc.opengamefinder.api.results.ChatLineMessageResult;
import com.ractoc.opengamefinder.api.results.ChatMessageResult;
import com.ractoc.opengamefinder.api.vo.AccountVO;
import com.ractoc.opengamefinder.server.plugins.messages.CloseSessionMessage;
import com.ractoc.pffj.api.BasePluginMessageResult;
import com.ractoc.pffj.api.PluginLoaderException;
import com.ractoc.pffj.api.PluginMessage;
import com.ractoc.pffj.api.PluginMessageResultListener;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;

/**
 * This plugin handles everything chat related. This is also where the list of
 * players is maintained for the chat rooms. <p/> <p/> <p/> <p/> <p/>
 * <p/>
 * @author ractoc
 * @version 0.1
 */
public class ChatPlugin extends BaseServerPlugin {

    private static Logger logger = Logger.getLogger(ChatPlugin.class.getName());
    private ConcurrentHashMap<String, ChatRoom> rooms = new ConcurrentHashMap<String, ChatRoom>();
    private String defaultRooms;

    /**
     * Default plugin.
     */
    public ChatPlugin() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void load() throws PluginLoaderException {
        if (!StringUtils.isEmpty(defaultRooms)) {
            String[] dr = defaultRooms.split(",");
            for (String roomId : dr) {
                createRoom(roomId.trim());
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void processServerMessage(final PluginMessage msg, final PluginMessageResultListener pmrListener) {
        ChatMessageResult result = null;
        logger.debug("processing: " + msg.getClass().getName());
        if (msg instanceof ChatCreateRoomMessage) {
            result = new ChatCreateRoomMessageResult();
            result.setErrorCode(MessageResultsErrorCodes.SUCCESS);
            ChatCreateRoomMessage ccrMsg = (ChatCreateRoomMessage) msg;
            createRoom(ccrMsg.getRoomId());
        } else if (msg instanceof ChatLineMessage) {
            ChatLineMessage clMsg = (ChatLineMessage) msg;
            result = (ChatMessageResult) rooms.get(clMsg.getRoomId()).processChatLine(clMsg);
        } else if (msg instanceof ChatEnterRoomMessage) {
            ChatEnterRoomMessage cerMsg = (ChatEnterRoomMessage) msg;
            logger.info("player " + cerMsg.getAccount().getName() + " is entering room " + cerMsg.getRoomId());
            result = (ChatMessageResult) rooms.get(cerMsg.getRoomId()).processChatEnterRoom(cerMsg);
            result.setRoomId(cerMsg.getRoomId());
        } else if (msg instanceof ChatLeaveRoomMessage) {
            ChatLeaveRoomMessage clrMsg = (ChatLeaveRoomMessage) msg;
            result = (ChatMessageResult) rooms.get(clrMsg.getRoomId()).processChatLeaveRoom(clrMsg);
            if (rooms.get(clrMsg.getRoomId()).isDestroyable() && rooms.get(clrMsg.getRoomId()).isEmpty()) {
                destroyRoom(clrMsg.getRoomId());
            }
            result.setRoomId(clrMsg.getRoomId());
        } else if (msg instanceof CloseSessionMessage) {
            final CloseSessionMessage csMsg = (CloseSessionMessage) msg;
            final ChatLeaveRoomMessage clrMsg = new ChatLeaveRoomMessage();
            final AccountVO player = new AccountVO();
            player.setId(csMsg.getPlayerId());
            clrMsg.setAccount(player);
            for (ChatRoom room : rooms.values()) {
                result = (ChatMessageResult) room.processChatLeaveRoom(clrMsg);
            }
            result.setRoomId(clrMsg.getRoomId());
        }
        pmrListener.receivedResult(result);
    }
    
    private void createRoom(String roomId) {
        logger.info("creating room " + roomId);
        if (rooms.containsKey(roomId)) {
            throw new IllegalArgumentException("Room already exists.");
        }
        rooms.put(roomId, new ChatRoom(this));
    }
    
    private void destroyRoom(String roomId) {
        if (!rooms.containsKey(roomId)) {
            throw new IllegalArgumentException("Room does not exist.");
        }
        rooms.remove(roomId);
    }
    
    public void setDefaultRooms(String defaultRooms) {
        this.defaultRooms = defaultRooms;
    }
    
    public String getDefaultRooms() {
        return defaultRooms;
    }

    private class ChatRoom {

        private List<AccountVO> players = new ArrayList<AccountVO>();
        private List<AccountVO> sortedPlayers = Collections.synchronizedList(players);
        private final ChatPlugin chatPlugin;
        
        private boolean destroyable = false;

        ChatRoom(ChatPlugin chatPlugin) {
            this.chatPlugin = chatPlugin;
        }

        ChatRoom(ChatPlugin chatPlugin, boolean destroyable) {
            this.chatPlugin = chatPlugin;
            this.destroyable = destroyable;
        }

        boolean isDestroyable() {
            return destroyable;
        }

        void setDestroyable(boolean destroyable) {
            this.destroyable = destroyable;
        }
        
        boolean isEmpty() {
            return players.isEmpty();
        }
        
        BasePluginMessageResult processChatLine(final PluginMessage msg) {
            BasePluginMessageResult result;
            final ChatLineMessage clMsg = (ChatLineMessage) msg;
            result = new ChatLineMessageResult();
            logger.debug("Session: " + sessionId + " received chatline from " + clMsg.getAccount().getName());
            for (AccountVO player : sortedPlayers) {
                if (!player.equals(clMsg.getAccount())) {
                    // now we can sent the chatline to the player.
                    sendNetworkMessage(result, clMsg, chatPlugin.getPluginId(), chatPlugin.getPluginId(), false, true, player.getId());
                }
            }
            result.equals(null);
            return result;
        }

        BasePluginMessageResult processChatEnterRoom(final PluginMessage msg) {
            BasePluginMessageResult result;
            final ChatEnterRoomMessage reMsg = (ChatEnterRoomMessage) msg;
            final ChatEnterRoomMessageResult cerResult = new ChatEnterRoomMessageResult();
            logger.debug("sorted players: " + sortedPlayers);
            // we only want to add the player to the room if the player isn't in
            // the room already.
            if (sortedPlayers.contains(reMsg.getAccount())) {
                cerResult.setErrorCode(MessageResultsErrorCodes.PLAYER_ALREADY_IN_ROOM);
            } else {
                // add the player to the set of players present in this room,
                // only when the player is not already in this room.
                sortedPlayers.add(reMsg.getAccount());
                logger.info("added player " + reMsg.getAccount().getName() + " to room " + reMsg.getRoomId());
                cerResult.setErrorCode(MessageResultsErrorCodes.SUCCESS);
                cerResult.setRoomMemberList(players);
                // send the enter room event to all the other players as well.
                result = new ChatEnterRoomMessageResult();
                for (AccountVO player : sortedPlayers) {
                    if (!player.equals(reMsg.getAccount())) {
                        // now we can sent the new player to the player.
                        sendNetworkMessage(result, reMsg, chatPlugin.getPluginId(), chatPlugin.getPluginId(), false, true, player.getId());
                    }
                }
            }
            result = cerResult;
            return result;
        }

        BasePluginMessageResult processChatLeaveRoom(final PluginMessage msg) {
            BasePluginMessageResult result;
            final ChatLeaveRoomMessage lrMsg = (ChatLeaveRoomMessage) msg;
            final ChatLeaveRoomMessageResult clrResult = new ChatLeaveRoomMessageResult();
            clrResult.setErrorCode(MessageResultsErrorCodes.SUCCESS);
            // remove the player from the set of players present in this room,
            final AccountVO removedPlayer = sortedPlayers.get(sortedPlayers.indexOf(lrMsg.getAccount()));
            sortedPlayers.remove(lrMsg.getAccount());
            lrMsg.setAccount(removedPlayer);
            logger.debug("player removed from the room: " + (!sortedPlayers.contains(lrMsg.getAccount())));
            // send the leave room event to all the other players as well.
            result = new ChatLeaveRoomMessageResult();
            for (AccountVO player : sortedPlayers) {
                if (!player.equals(removedPlayer)) {
                    // now we can sent the new player to the player.
                    sendNetworkMessage(result, lrMsg, chatPlugin.getPluginId(), chatPlugin.getPluginId(), false, true, player.getId());
                }
            }
            logger.info("removed player " + lrMsg.getAccount().getId() + " from room");
            return clrResult;
        }
    }
}
